<?php


namespace App\Service;

use EchoPHP\Facades\Redis;

class ImgCaptcha {

    private $charset = 'abcdefghkmnprstuvwxyz2345678'; //随机因子
    private $width = 70; //宽度
    private $height = 28; //高度
    private $font; //指定的字体

    private $imgHandler = null;   //图片句柄

    protected $codeLength = 4;//随机码长度
    protected $randCode = '';//随机码
    protected $bgColor = '#FFFFFF';//图片背景颜色
    protected $codeColor = '';//字符颜色
    protected $codeSize;            //code 的字体大小


    /**
     * ImgCaptcha constructor.
     * 构建图形验证码参数
     */
    public function __construct() {
        //随机字体大小
        $this->codeSize = rand(19, 19);
        //随机文字颜色
        $codeColor = array('#CD5C5C', '#0000CD');
        $this->codeColor = $codeColor[array_rand($codeColor)];
        //随机字体
        $fonts = array('arial.ttf', 'past.ttf', 'Capital.ttf');
        $this->font = ROOT_PATH . '/Data/ttf/' . $fonts[array_rand($fonts)];
    }

    /**
     * 输出图片
     * @param $validateCode     验证码内容
     * @param array $options 附加参数
     *      level  复杂度  1|2|3  数字越大越复杂
     *      type    输出类型 img|source 输出图片或者base64编码的图片
     *      width   图片宽度
     *      height  图片高度
     * @return \EchoPHP\Http\Response\StreamedResponse
     * @throws \Exception
     */
    public function response($validateCode, array $options = []) {
        if (!empty($validateCode)) {
            $this->randCode = $validateCode;
            $this->codeLength = strlen($this->randCode);
        } else {
            $this->randCode = $this->createCode($this->codeLength);
        }
        $options = array_merge([
            'level' => 2,
            'type' => 'img',
        ], $options);

        $this->init($options);
        //创建图片
        $this->createClearImg();
        //添加噪点
        $this->addNoise($options['level']);
        //输出图片
        return $this->outPut($options['type']);
    }

    /**
     * 初始化图片参数数据
     * @param $options
     */
    private function init($options) {
        $this->width = $options['width'] ?? 70;
        $this->height = $options['height'] ?? 28;
    }


    /**
     * 创建干净的不带噪点的图片句柄
     */
    private function createClearImg() {
        //创建宽：width，高：height的白色图片
        $this->imgHandler = imagecreatetruecolor($this->width, $this->height);
        $bg_color_info = $this->getColorInfo($this->bgColor);
        $imgBgColor = imagecolorallocate($this->imgHandler, $bg_color_info[0], $bg_color_info[1], $bg_color_info[2]);
        imagefill($this->imgHandler, 0, 0, $imgBgColor);

        //设置文字颜色
        $codeColorInfo = $this->getColorInfo($this->codeColor);
        $codeColor = ImageColorAllocate($this->imgHandler, $codeColorInfo[0], $codeColorInfo[1], $codeColorInfo[2]);

        //计算 TTF 文字所占区域。
        $ttfBox = imagettfbbox($this->codeSize, 0, $this->font, $this->randCode);
        $textWidth = abs($ttfBox[2] - $ttfBox[0]) - 20;
        $perSide = floor(($this->width - $textWidth) / 3);

        //往图片中写入文字
        $arr = array(-1, 1);
        for ($i = 0; $i < $this->codeLength; $i++) {
            $angle = $arr[array_rand($arr)] * mt_rand(5, 15);
            imagettftext($this->imgHandler, $this->codeSize, $angle, $perSide + $i * 13, $this->height - 5, $codeColor, $this->font, $this->randCode{$i});
        }
    }

    /**
     * 功能：    颜色分离为三色如#FFFFFF => 255 , 255 , 255
     * 函数名：    get_color_info
     * @param $color
     * @return array
     */
    private function getColorInfo($color) {
        return array(hexdec($color[1] . $color[2]), hexdec($color[3] . $color[4]), hexdec($color[5] . $color[6]));
    }

    /**
     * 生成随机码
     * @param int $codeLength 验证码长度
     * @return string
     */
    private function createCode($codeLength = 4) {
        $_len = strlen($this->charset) - 1;
        $code = '';
        for ($i = 0; $i < $codeLength; $i++) {
            $code .= $this->charset[mt_rand(0, $_len)];
        }
        return $code;
    }

    /**
     * 添加噪点
     * @param $level
     */
    private function addNoise($level) {
        switch ($level) {
            case 1:
                //最低级别，添加少量噪点直线
                $pixelCount = 30;
                $lineCount = 2;
                $sinLineCount = 0;
                $imgRotate = false;
                break;
            case 3:
                //复杂级别，添加基本无法肉眼识别噪点
                $pixelCount = 100;
                $lineCount = 0;
                $sinLineCount = 1;
                $imgRotate = true;
                break;
            case 2:
            default:
                //普通级别，添加肉眼基本可识别的噪点
                $pixelCount = 100;
                $lineCount = 6;
                $sinLineCount = 0;
                $imgRotate = false;
                break;
        }
        //添加像素噪点
        for ($i = 0; $i < $pixelCount; $i++) {
            $randColor = ImageColorallocate($this->imgHandler, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
            imagesetpixel($this->imgHandler, mt_rand() % $this->width, mt_rand() % $this->height, $randColor);
        }
        //添加直线噪点
        for ($i = 0; $i < $lineCount; $i++) {
            $color = imagecolorallocate($this->imgHandler, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
            imageline($this->imgHandler, mt_rand(0, $this->width), mt_rand(0, $this->height), mt_rand(0, $this->width), mt_rand(0, $this->height), $color);
        }
        //添加正弦线噪点
        for ($i = 0; $i < $sinLineCount; $i++) {
            $this->addSinLine();
        }
        //旋转图片
        if ($imgRotate) {
            $this->addRotate();
        }

    }

    /**
     * 正弦干扰线
     */
    private function addSinLine() {
        $lineColorInfo = $this->getColorInfo($this->codeColor);
        $lineColor = ImageColorAllocate($this->imgHandler, $lineColorInfo[0], $lineColorInfo[1], $lineColorInfo[2]);

        $A = mt_rand(1, $this->height / 2);                  // 振幅
        $b = mt_rand(0 - $this->height / 4, $this->height / 4);   // Y轴方向偏移量
        $f = mt_rand(0 - $this->height / 4, $this->height / 4);   // X轴方向偏移量
        $T = mt_rand($this->height * 1.5, $this->width * 2);  // 周期
        $w = (2 * M_PI) / $T;

        if ($w == 0) {
            return;
        }
        for ($px = 0; $px <= $this->width; $px += 0.9) {
            $py = $A * sin($w * $px + $f) + $b + $this->height / 2;  // y = Asin(ωx+φ) + b
            for ($i = 0; $i < 3; $i++) {
                imagesetpixel($this->imgHandler, $px + $i, $py + $i, $lineColor);
            }
        }

    }

    /**
     * 添加图片旋转
     */
    private function addRotate() {
        if (is_null($this->imgHandler)) {
            return;
        }

        $img = imagecreatetruecolor($this->width, $this->height);
        $bgColorInfo = $this->getColorInfo($this->bgColor);
        $imgBgColor = imagecolorallocate($img, $bgColorInfo[0], $bgColorInfo[1], $bgColorInfo[2]);
        imagefill($img, 0, 0, $imgBgColor);

        $distortion = floor(0.015 * rand(3, 4) * $this->width) - 2;       // 扭曲程度
        $randDistortionSide = rand(0, 2);                            // 扭曲方向，向左、向右
        for ($i = 0; $i < $this->width; $i++) {
            for ($j = 0; $j < $this->height; $j++) {
                $rgb = imagecolorat($this->imgHandler, $i, $j);
                if ($randDistortionSide) {
                    if ((int)($i + sin($j / $this->height * 2 * M_PI) * $distortion) <= $this->width && (int)($i + sin($j / $this->height * 2 * M_PI) * $distortion) >= 0) {
                        imagesetpixel($img, (int)($i + sin($j / $this->height * 2 * M_PI + M_PI * 0.1) * $distortion), $j, $rgb);
                    }
                } else {
                    if ((int)($i - sin($j / $this->height * 2 * M_PI) * $distortion) <= $this->width && (int)($i - sin($j / $this->height * 2 * M_PI) * $distortion) >= 0) {
                        imagesetpixel($img, (int)($i - sin($j / $this->height * 2 * M_PI - M_PI * 0.1) * $distortion), $j, $rgb);
                    }
                }
            }
        }

        imagedestroy($this->imgHandler);
        $this->imgHandler = $img;
    }

    /**
     * 输出验证码
     * @param string $type img 直接输出图片 source 输出图片base64后的数据
     * @return \EchoPHP\Http\Response\StreamedResponse
     * @throws \Exception
     */
    private function outPut($type = 'img') {
        ob_start();
        imagepng($this->imgHandler);
        imagedestroy($this->imgHandler);
        $content = ob_get_clean();

        if ($type == 'img') {
            return response($content, 200, [
                'Content-Type' => 'image/png',
            ]);
        }
        return 'data:image/png;base64,' . base64_encode($content);
    }

    /**
     * 释放资源
     */
    public function __destruct() {
        unset($this->imgHandler);
    }

}